package gameobjects;

import interfaces.Collidable;
import interfaces.CollisionHandler;
import interfaces.Controllable;
import interfaces.Movable;
import interfaces.Visible;
import java.awt.Graphics;
import java.awt.Point;
import resources.Sound;
import resources.Sprite;
import screens.StatisticsScreen;
import tools.Bounds;
import tools.CollisionGrid;
import tools.Direction;
import tools.MageKeyControl;
import abstracts.Level;
import abstracts.Monster;
import abstracts.Task;

/** Merlin. */
public class Mage implements Visible, Movable, Controllable, CollisionHandler {
    /** */
    private static final double SPEED = 2;

    /** */
    private final Level level;
    /** */
    private Bounds bounds;
    /** */
    private Point.Double vel;
    /** */
    private Direction dir;
    /** */
    private final MageKeyControl mkc;
    /** */
    private double reload = 0;
    /** */
    private Sprite sprite = Sprite.MAGE_STAND_DOWN;

    /**
     * Creates a mage.
     * 
     * @param currentLevel - the level in which to create the mage.
     * @param row - the row in which to create the mage.
     * @param col - the column in which to create the mage.
     */
    public Mage(final Level currentLevel, final int row, final int col) {
        bounds = new Bounds(row, col, 24, 24);
        vel = new Point.Double();

        dir = Direction.DOWN;

        level = currentLevel;

        mkc = new MageKeyControl();
        level.getApplet().getCanvas().addKeyListener(mkc);
        level.getApplet().getCanvas().requestFocus();
    }

    @Override
    public final void control() {
        // Set velocity
        if (mkc.getSpd() != 0) {
            dir = mkc.getDir();
            vel.x = dir.X * SPEED;
            vel.y = dir.Y * SPEED;
        } else {
            vel.setLocation(0, 0);
        }

        if (mkc.getShoot() && reload > 1) {
            shoot();
            reload = 0;
        }
        reload += 0.05;

        if (mkc.getAction()) {
            action();
        }
    }

    /** Creates a {@linkplain Fireball}. */
    public final void shoot() {
        level.add(new Fireball(bounds.getCenterX(), bounds.getCenterY(), dir,
                level));
    }

    /** Checks for any possible actions and performs them. */
    public final void action() {
        // Check for action objects
        for (Collidable c : level.getCollisionGrid().getCollidables(bounds)) {
            if (c instanceof Teleporter) {
                final Teleporter teleporter = (Teleporter) c;
                Sound.TELEPORT.play();

                // Hide the mage
                level.remove(this);

                TaskQueue tasks = new TaskQueue() {
                    {
                        add(new ShrinkAnimation(bounds, sprite, 0.0625));
                        add(new Task() {
                            @Override
                            public boolean isDone() {
                                // Move the mage
                                bounds.x = teleporter.getDest().getCenterX()
                                        - bounds.width / 2;
                                bounds.y = teleporter.getDest().getCenterY()
                                        - bounds.height / 2;
                                return true;
                            }
                        });
                        add(new GrowAnimation(bounds, sprite, 0.0625));
                        add(new Task() {
                            @Override
                            public boolean isDone() {
                                // Show the mage
                                level.add(Mage.this);
                                return true;
                            }
                        });
                    }
                };
                level.add(tasks);
                tasks.run(level);
            }
        }
    }

    @Override
    public final void move() {
        bounds.x += vel.x;
        if (level.getCollisionGrid().hasSolid(getBounds())) {
            // Handle collision
            bounds.x -= vel.x;

            // Binary search to find closest point to wall
            do {
                vel.x /= 2;
                bounds.x += vel.x;
                if (level.getCollisionGrid().hasSolid(getBounds())) {
                    bounds.x -= vel.x;
                }
            } while (vel.x > 0.2);
        }

        bounds.y += vel.y;
        if (level.getCollisionGrid().hasSolid(getBounds())) {
            // Handle collision
            bounds.y -= vel.y;

            // Binary search to find closest point to wall
            do {
                vel.y /= 2;
                bounds.y += vel.y;
                if (level.getCollisionGrid().hasSolid(getBounds())) {
                    bounds.y -= vel.y;
                }
            } while (vel.y > 0.2);
        }
    }

    /** Deletes the mage, runs the death animation and ends the level. */
    public final void death() {
        Sound.MAGE_DEATH.play();
        level.remove(this);

        TaskQueue tasks = new TaskQueue();
        tasks.add(new ShrinkAnimation(bounds, sprite, 0.0625));
        tasks.add(new Task() {
            @Override
            public boolean isDone() {
                level.getApplet().setScreen(
                        new StatisticsScreen(level.getApplet()));
                return true;
            }
        });
        level.add(tasks);
        tasks.run(level);
    }

    @Override
    public final void collide(final CollisionGrid cg) {
        for (Collidable other : cg.getCollidables(bounds)) {
            if (other instanceof Treasure) {
                ((Treasure) other).collect();
            }

            if (other instanceof Monster) {
                death();
            }
        }
    }

    @Override
    public final void draw(final Graphics g) {
        sprite.drawAnchored(g, 0, bounds);
    }

    @Override
    public final double getDepth() {
        return bounds.getMaxY();
    }

    @Override
    public final Bounds getBounds() {
        return bounds;
    }
}
